﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpSphinx.Util.Configuration;
using System.Runtime.Serialization;

namespace SharpSphinx.Util
{
    public class LogMath : IConfigurable , ISerializable
    {
      public const String PROP_LOG_BASE = "LogBase";


      public const String PROP_USE_ADD_TABLE = "useAddTable"; 

      private const  float logZero = -float.MaxValue;

      private float logOne;


      private float logBase;
      private bool useAddTable;


        private float NaturalLogBase;
        private float InverseNaturalLogBase;
        private List<float> theAddTable = new List<float>(); 
        private float MaxLogValue; 
        private float MinLogValue; 



        public LogMath(float logbase, bool UseAddTable) {
        

             /// LOGGER 
            this.logBase = logbase; 
            this.useAddTable = UseAddTable;

            Init();

        }


        public LogMath() { 
        
        }

        public override void newProperties(PropertySheet ps) {
            // logger 

            logBase = ps.getFloat(PROP_LOG_BASE); 
            useAddTable = ps.getBoolean(PROP_USE_ADD_TABLE);


            Init();
        }


        private void Init() {
            /// need a Logger 
            /// 


            NaturalLogBase = (float) Math.Log(logBase); 
            InverseNaturalLogBase = 1.0f / NaturalLogBase;


            MaxLogValue = LinearToLog(Double.MaxValue);
            MinLogValue = LinearToLog(Double.MinValue);

            if (useAddTable)
            {

                float innerSummation;
                int entriesInTheAddTable;
                const int VeryLargeNumberOfEntries = 150000;
                const int VerySmallNumberOfEntries = 0;


                entriesInTheAddTable = (int)-Math.Round(LinearToLog(LogToLinear(0.5f) - 1));


                if (entriesInTheAddTable > VeryLargeNumberOfEntries)
                {
                    entriesInTheAddTable = VeryLargeNumberOfEntries;
                }

                if (entriesInTheAddTable <= VerySmallNumberOfEntries)
                {
                    throw new Exception("The Log base " + logBase + " yields a very small AddTable."); 
                }

                theAddTable = new List<float>(entriesInTheAddTable);
                for (int index = 0; index < entriesInTheAddTable; index++)
                {
                    innerSummation = (float)LogToLinear(-index);
                    innerSummation += 1.0f;
                    theAddTable[index] = LinearToLog(innerSummation);
                }

            }
        }


       public float AddAsLinear(float logVal1 , float logVal2 )
        {
           float logHighestValue = logVal1; 
           float logDifference = logVal1 - logVal2; 
 


           if (logDifference < 0) {
               logHighestValue = logVal2; 
               logDifference = -logDifference; 
           }
           return logHighestValue + AddTable(logDifference);
    
        }


        private float AddTable(float index)
        {

            if (useAddTable)
            {

                int intIndex = (int)(index + 0.5);


                if (0 <= intIndex)
                {
                    if (intIndex < theAddTable.Count)
                    {
                        return theAddTable[intIndex];
                    }
                    else
                    {
                        return 0.0f;
                    }
                }
                else
                {

                    throw new Exception("AddTable Index has to be negative");
                }
            }
            else
            {
                return AddTableActualComputation(index);
            }
        }
       

  
        private float AddTableActualComputation(float index)
        {

            double logInnerSummation;

            logInnerSummation = LogToLinear(-index);
            logInnerSummation += 1.0;
            return LinearToLog(logInnerSummation);
        }

        public float SubstractAsLinear(float logMinued, float logSubtrahend)
        {
            double logInnerSummation;
            if (logMinued < logSubtrahend)
            {
                throw new Exception("Subtracting results in log of a negative number: " + logMinued + " - " + logSubtrahend);
            }

            logInnerSummation = 1.0;
            logInnerSummation -= LogToLinear(logSubtrahend - logMinued);
            return logMinued + LinearToLog(logInnerSummation);
        }


        /// <summary>
        /// Converts the source, which is assumed to be a log value whose base is sourceBase, to a log value whose base is
        /// resultBase. Possible values for both the source and result bases include Math.E, 10.0, LogMath.getLogBase(). If a
        /// source or result base is not supported, an IllegalArgumentException will be thrown. <p/> <p/> It takes advantage
        /// of the relation: </p> <p/> <p/> <b>log_a(b) = log_c(b) / lob_c(a) </b> </p> <p/> <p/> or: </p> <p/> <p/>
        /// <b>log_a(b) = log_c(b) * lob_a(c) </b> </p> <p/> <p/> where <b>log_a(b) </b> is logarithm of <b>b </b> base <b>a
        /// </b> etc. </p>
        /// </summary>
        /// <param name="logSource">log value whose base is sourceBase</param>
        /// <param name="SourceBase">the base of the log the source</param>
        /// <param name="ResultBase">the base to convert the source log to</param>
        /// <returns></returns>
        public static float LogToLog(float logSource, float SourceBase,float ResultBase)
        {
            if ((SourceBase <= 0) || (ResultBase <= 0))
            {
                throw new Exception("Trying to take log of non-positive number " + SourceBase + " or " + ResultBase);
            }

            if (logSource == logZero)
            { return logZero;  }


            float lnSourceBase = (float)Math.Log(SourceBase);
            float lnResultBase = (float)Math.Log(ResultBase);
            return (logSource * lnSourceBase / lnResultBase); 
        }

        public float LnToLog(float logSource)
        {
            if (logSource == logZero)
            {
                return logZero;
            }

            return (logSource * InverseNaturalLogBase);
        }

        public float Log10ToLog(float logSource)
        {
            if (logSource == logZero)
            {
                return logZero;
            }
            return LogToLog(logSource, 10.0f, logBase);
        }

        public float LogToLn(float logSource)
        {
            if (logSource == logZero) {
                return logZero;
            }

            return logSource * NaturalLogBase;
        }


        public float LinearToLog(double LinearValue)
        {
            double returnValue;

            if (LinearValue < 0.0)
            {
                throw new Exception("LinearToLog: Param must be >= 0:  " + LinearValue);
            }
            else if (LinearValue == 0.0)
            {
                return LogZero;
            }
            else
            {
                returnValue = Math.Log(LinearValue) * InverseNaturalLogBase;
                if (returnValue > float.MaxValue)
                {
                    return float.MaxValue;
                }
                else
                {
                    if (returnValue < -float.MaxValue)
                    {
                        return -float.MaxValue;
                    }
                    else
                    {
                        return (float)returnValue;
                    }
                }

            }
        }


        public double LogToLinear(float logValue)
        {
            double returnValue;
            if (logValue < MinLogValue)
            {
                returnValue = 0.0;
            }
            else if (logValue > MaxLogValue)
            {
                returnValue = double.MaxValue;
            }
            else
            {
                returnValue = Math.Exp(LogToLn(logValue));
            }
            return returnValue;
        }

        public static float LogZero
        {
            get { return logZero; }
        }

        public float LogOne
        {
            get { return logOne; }
        }

        public float LogBase
        {
            get { return logOne; }
        }

        public bool IsUseAddTable
        {
            get { return useAddTable; }
        }

        public static float Log10(float value)
        {
            return (float)(0.4342944819 * Math.Log(value));
        }


        public void LinearToLog(float[] vector)
        {
            int nbGaussians = vector.Length;
            for (int i = 0; i < nbGaussians; i++)
            {
                vector[i] = LinearToLog(vector[i]);
            }
        }


        public void LogToLinear(float[] vector, float[] Out)
        {
            for (int i = 0; i < vector.Length; i++)
            {
                Out[i] = (float)LogToLinear(vector[i]);
            }
        }
    }
}
